package com.freetymekiyan.algorithms.other;

/**
 * Given two sorted arrays A, B of size m and n respectively. Find the k-th
 * smallest element in the union of A and B. You can assume that there are no
 * duplicate elements.
 * 
 * Tags: Array, 
 */
class KthSmallestSortedArrays {
    public static void main(String[] args) {
        int[] A = { 1, 2, 3, 4, 5, 6 };
        int[] B = { 2, 3, 4, 5, 6, 7 };
        System.out.println(kthSmallest(0, A, B));
        System.out.println(kthSmallest(1, A, B));
        System.out.println(kthSmallest(10, A, B));
        System.out.println(kthSmallest(11, A, B));
        System.out.println(kthSmallest(12, A, B));
        System.out.println(kthSmallest(13, A, B));
    }

    /**
     * Binary Search of A and B
     * O(lg m + lg n)
     * make i + j = k - 1
     * If Bj-1 < Ai < Bj, then Ai must be the k-th smallest,
     * or else if Ai-1 < Bj < Ai, then Bj must be the k-th smallest.
     */
    public static double kthSmallestRec(int k, int[] A, int[] B) {
        /*keep smaller array first one*/
        int n = A.length;
        int m = B.length;
        if (n > m)
            return kthSmallestRec(k, B, A);

        int mid = (n + m - 1) / 2;
        int l = 0, r = Math.min(mid, n); // r is n, NOT n-1, this is important!!
        while (l < r) {
            int midA = (l + r) / 2;
            int midB = mid - midA;
            if (A[midA] < B[midB])
                l = midA + 1;
            else
                r = midA;
        }

        // after binary search, we almost get the median because it must be between these 4 numbers: A[l-1], A[l], B[k-l], and B[k-l+1]
        int a = Math.max(l > 0 ? A[l - 1] : Integer.MIN_VALUE, mid - l >= 0 ? B[mid - l] : Integer.MIN_VALUE);
        if (((n + m) & 1) == 1) // total number is odd
           return (double) a;

        /*total number is even*/
        int b = Math.min(l < n ? A[l] : Integer.MAX_VALUE, mid - l + 1 < m ? B[mid - l + 1] : Integer.MAX_VALUE);
        return (a + b) / 2.0; // median of 2
    }

    /**
     * Two pointers start from the head of A & B
     * O(k)
     */
    public static int kthSmallest(int k, int[] A, int[] B) {
        k -= 1;
        int lenA = A.length;
        int lenB = B.length;
        if (k < 0 || k > lenA + lenB - 1) return -1; // out of range, 1 <= k <= A.length + B.length
        if (k == 0) return A[0] < B[0] ? A[0] : B[0];
        if (k == lenA + lenB - 1) return A[lenA - 1] < B[lenB - 1] ? B[lenB - 1] : A[lenA - 1];

        int i = 0;
        int j = 0;
        while (i + j < k) {
            if (A[i] < B[j] && i < A.length) {
                i++;
            } else if (A[i] >= B[j] && j < B.length){
                j++;
            }
            if (i + j == k && A[i] < B[j]) {
                return A[i];
            } else if (i + j == k && A[i] >= B[j]) {
                return B[j];
            }
        }
        return -1;
    }
}
